﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace MultipleQueue.Simulation
{
    public class Model
    {
        public struct CustomersQueue
        {
            public float time;
            public int queueSize;
        };

        private List<CustomersQueue> customersQueueGraph = new List<CustomersQueue>();

        public List<CustomersQueue> CustomersQueueGraph
        {
            get
            {
                return this.customersQueueGraph;
            }
        }

        static float lastArrivalTime = 0;

        /// <summary>
        /// Number of servers
        /// </summary>
        private int serversCount;

        /// <summary>
        /// The server priority rule
        /// </summary>
        private ServerSelection serverSelection;

        /// <summary>
        /// Customers inter-arrival distribution
        /// </summary>
        private Distributions.IDistribution customerInterArrivalDistribution;

        /// <summary>
        /// Number of customer at which to stop simulation.
        /// </summary>
        private int stoppingCondition;

        /// <summary>
        /// List of servers
        /// </summary>
        private List<Entities.Server> servers;
        

        /// <summary>
        /// Gets the servers.
        /// </summary>
        public List<Entities.Server> Servers
        {
            get { return servers; }
        }

        /// <summary>
        /// The customers queue, the queue is changed with time according to the current number of customer waiting.
        /// </summary>
        private Queue<MultipleQueue.Entities.Customer> customerQueue;

        /// <summary>
        /// The list of customers who finished their service.
        /// </summary>
        private List<MultipleQueue.Entities.Customer> completedCustomers;

        /// <summary>
        /// Contains the future events sorted by time ASC
        /// </summary>
        private SortedList<MultipleQueue.Entities.Customer, EventType> eventsList;

        /// <summary>
        /// The clock of the simulation, changes to the time of the next event with every cycle of the run loop
        /// </summary>
        private float clock;
        /// <summary>
        /// Average waiting time of customers in the system
        /// </summary>
        private float averageWaitingTime = 0;

        private DataTable averageServiceTime;

        public List<MultipleQueue.Entities.Customer> CompletedCustomers
        {
            get
            {
                return this.completedCustomers;
            }
        }

        public DataTable AverageServiceTime
        {
            get
            {
                return this.averageServiceTime;
            }
        }

        private DataTable customersTable;

        public DataTable CustomerTable
        {
            get
            {
                return this.customersTable;
            }
        }
        private DataTable idleServerProbability;

        public DataTable IdleServerProbability
        {
            get
            {
                return this.idleServerProbability;
            }
        }

        /// <summary>
        /// Gets or sets the average waiting time of customers in the system.
        /// </summary>
        /// <value>
        /// The average waiting time.
        /// </value>
        public float AverageWaitingTime
        {
            get { return averageWaitingTime; }
            set { averageWaitingTime = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        private float probabilityOfWait = 0;

        /// <summary>
        /// Gets or sets the probability that a customer will wait in the system.
        /// </summary>
        /// <value>
        /// The probability of wait.
        /// </value>
        public float ProbabilityOfWait
        {
            get { return probabilityOfWait; }
            set { probabilityOfWait = value; }
        }
        private string stoppingConditionType;

        /// <summary>
        /// Initializes a new instance of the <see cref="Model"/> class.
        /// </summary>
        /// <param name="priorityType">Type of the priority.</param>
        /// <param name="customerInterArrivalDistribution">The customer inter arrival distribution.</param>
        /// <param name="servers">The servers.</param>
        /// <param name="customerCountStoppingCondition">The customer count stopping condition.</param>
        /// <param name="lcgRandomNumberGenerator">The LCG random number generator.</param>
        public Model(ServerSelection serverSelection,
            Distributions.IDistribution customerInterArrivalDistribution,
            List<Entities.Server> servers, int stoppingConditionValue, string stoppingConditionType)
        {
            this.customerInterArrivalDistribution = customerInterArrivalDistribution;
            this.serverSelection = serverSelection;
            this.serversCount = servers.Count;
            this.stoppingCondition = stoppingConditionValue;
            this.completedCustomers = new List<MultipleQueue.Entities.Customer>();
            this.customerQueue = new Queue<MultipleQueue.Entities.Customer>();
            this.eventsList = new SortedList<MultipleQueue.Entities.Customer, EventType>();
            this.servers = servers;
            this.stoppingConditionType = stoppingConditionType;
        }
        bool firstCustomer = true;
        /// <summary>
        /// Initializes this instance.
        /// </summary>
        private void Initialize()
        {
            this.clock = 0;
            //Entities.Customer firstCustomer = this.GenerateCustomer();
            //this.ArriveCustomer(firstCustomer);
            //this.eventsList.Add(firstCustomer, EventType.Arrival);
            Entities.Customer customers;
            if (this.stoppingConditionType.Equals("number"))
            {
                for (int i = 0; i < this.stoppingCondition; i++)
                {
                    customers = this.GenerateCustomer();
                    eventsList.Add(customers, EventType.Arrival);
                }
            }
            else
            {
                customers = this.GenerateCustomer();
                eventsList.Add(customers, EventType.Arrival);
                this.ArriveCustomer(customers);
            }


            //throw new NotImplementedException(@"Implement Initialize in Simulation\Model.cs");

            //initialize clock time

            //Generate initial arrival Time
            //1-generate the first event -> get next customer                                 
            //2-add arrival event in the events list
        }

        /// <summary>
        /// Generates the customer.
        /// </summary>
        /// <returns></returns>
        private Entities.Customer GenerateCustomer()
        {
            float random = LCGRandomNumberGenerator.GetVariate();
            float interarrivalTime;
            if (!firstCustomer)
            {
                interarrivalTime = this.customerInterArrivalDistribution.GetValue(random);
            }
            else
            {
                interarrivalTime = 0;
                firstCustomer = false;
            }
            Entities.Customer customer;
            lastArrivalTime += interarrivalTime;
            customer = new Entities.Customer(interarrivalTime, lastArrivalTime);

            //eventsList.Add(customer, EventType.Arrival);
            return customer;

            //throw new NotImplementedException(@"Implement GenerateCustomer in Simulation\Model.cs");
            
            //use LCGRandomNumberGenerator to generate inter-arrival time for the customer
            //return the new customer object
        }

        /// <summary>
        /// Arrives the customer.
        /// </summary>
        /// <param name="arrivingCustomer">The arriving customer.</param>
        private void ArriveCustomer(Entities.Customer arrivingCustomer)
        {
            clock = arrivingCustomer.ArrivalTime;
            int serverAvailable = this.serverSelection.GetServerByPriority(this.servers, this.clock);
            if (serverAvailable == -1)
            {
                this.customerQueue.Enqueue(arrivingCustomer);
                CustomersQueue queuedCustomer;
                queuedCustomer.time = this.clock;
                queuedCustomer.queueSize = this.customerQueue.Count;
                this.customersQueueGraph.Add(queuedCustomer);
                
                arrivingCustomer.WaitTime = this.clock;
                eventsList.RemoveAt(0);
            }
            else
            {
                this.AssignCustomerToServer(arrivingCustomer, serverAvailable, false);
            }

            if (this.stoppingConditionType.Equals("time"))
            {
                eventsList.Add(this.GenerateCustomer(), EventType.Arrival);
            }

            //this.GenerateCustomer();
            //throw new NotImplementedException(@"Implement ArriveCustomer in Simulation\Model.cs");

            //set clock
            //select a server, if any are available
            //enter queue or assign to a server
            //schedule arrival of next customer
        }

        /// <summary>
        /// Departs the customer.
        /// </summary>
        /// <param name="departingCustomer">The departing customer.</param>
        private void DepartCustomer(Entities.Customer departingCustomer)
        {
            clock = departingCustomer.DepartureTime;
            servers[departingCustomer.ServerIndex].IsBusy = false;
            this.completedCustomers.Add(departingCustomer);
            eventsList.RemoveAt(0);
            //if (this.customerQueue.Count == 0)
            //{
            //    this.ArriveCustomer(this.GenerateCustomer());
            //}
            if (this.customerQueue.Count > 0)
            {
                Entities.Customer exitingCustomer = this.customerQueue.Dequeue();
                CustomersQueue queuedCustomer;
                queuedCustomer.time = this.clock;
                queuedCustomer.queueSize = this.customerQueue.Count;
                this.customersQueueGraph.Add(queuedCustomer);

                exitingCustomer.WaitTime = clock - exitingCustomer.WaitTime;
                this.AssignCustomerToServer(exitingCustomer, this.serverSelection.GetServerByPriority(servers, this.clock),true);
            }

            if (this.stoppingConditionType.Equals("time"))
            {
                eventsList.Add(this.GenerateCustomer(), EventType.Arrival);
            }

            //throw new NotImplementedException(@"Implement DepartCustomer in Simulation\Model.cs");

            //Set the clock to the current event.
            //The server is no longer busy.
            //Add the departed customer to the list of completed customers.
            //enter another customer to the servers, 
            //but still by ServerSelection because maybe a more efficient server is idle too.

            //We know that serverIndex should not be -1 because a server just go IDLE.
            //But we are using server Selection cause maybe another server is IDLE too, and better
        }

        /// <summary>
        /// Assigns the customer to server.
        /// </summary>
        /// <param name="customer">The customer.</param>
        /// <param name="serverIndex">Index of the server.</param>
        private void AssignCustomerToServer(Entities.Customer customer, int serverIndex, bool queue)
        {
            customer.IsArrived = true;
            customer.ServiceStartTime = this.clock;
            customer.ServerIndex = serverIndex;
            customer.ServiceTime = servers[serverIndex].GetNextServerTimeDistribution();
            
            customer.DepartureTime = customer.ArrivalTime + customer.WaitTime + customer.ServiceTime;
            if (!queue)
            {
                eventsList.RemoveAt(0);
            }
            eventsList.Add(customer, EventType.Departure);

            Entities.ServerBusyTime busy = new Entities.ServerBusyTime(customer.ArrivalTime,customer.DepartureTime);
            servers[serverIndex].IsBusy = true;
            servers[serverIndex].ServerStatusList.Add(busy);
            servers[serverIndex].TotalCustomerServed++;
            
            float tempUtilization = 0;
            for(int i = 0; i < servers[serverIndex].ServerStatusList.Count; i++)
            {
                tempUtilization += (servers[serverIndex].ServerStatusList[i].ToTime - servers[serverIndex].ServerStatusList[i].FromTime);
            }
            servers[serverIndex].Utilization = tempUtilization / this.clock;
            



            //throw new NotImplementedException(@"Implement AssignCustomerToServer in Simulation\Model.cs");

            //update customer's data
            //schedule the departure of this customer
            //update this server's statistics
        }
        /// <summary>
        /// Runs this instance.
        /// </summary>
        public void Run()
        {
            this.Initialize();
            if (this.stoppingConditionType.Equals("number"))
            {
                while (completedCustomers.Count < this.stoppingCondition)
                {
                    if (eventsList.Values[0] == EventType.Arrival)
                    {
                        this.ArriveCustomer(eventsList.Keys[0]);
                    }
                    else
                    {
                        this.DepartCustomer(eventsList.Keys[0]);
                    }
                }
            }
            else
            {
                while (this.clock < this.stoppingCondition)
                {
                    if (eventsList.Values[0] == EventType.Arrival)
                    {
                        this.ArriveCustomer(eventsList.Keys[0]);
                    }
                    else
                    {
                        this.DepartCustomer(eventsList.Keys[0]);
                        eventsList.RemoveAt(0);
                    }
                }
            }

            //throw new NotImplementedException(@"Implement Run in Simulation\Model.cs");

            //initialize model
            //while the stopping condition is not reached yet, handle the next event in the events list
            //calculate the system performance
        }
        
        /// <summary>
        /// Calculates the system performance.
        /// </summary>
        public void CalculateSystemPerformance()
        {
            customersTable = new DataTable();
            this.averageServiceTime = new DataTable();
            this.idleServerProbability = new DataTable();

            customersTable.Columns.Add("Customer Number");
            customersTable.Columns.Add("Interarrival Time");
            customersTable.Columns.Add("Arrival Time");
            customersTable.Columns.Add("Server Index");
            customersTable.Columns.Add("Time Service Begins");
            customersTable.Columns.Add("Service Duration");
            customersTable.Columns.Add("Time Service Ends");
            customersTable.Columns.Add("Total Delay");

            this.averageServiceTime.Columns.Add("Server Index");
            this.averageServiceTime.Columns.Add("Average Service Time");

            this.idleServerProbability.Columns.Add("Server Index");
            this.idleServerProbability.Columns.Add("Probability of Idle Service Time");

            DataRow customer;
            float totalWaitTime = 0;
            int customersWaited = 0;
            for (int i = 0; i < this.completedCustomers.Count; i++)
            {
                customer = customersTable.NewRow();
                customer[0] = i + 1;
                customer[1] = this.completedCustomers[i].InterarrivalTime;
                customer[2] = this.completedCustomers[i].ArrivalTime;
                customer[3] = this.completedCustomers[i].ServerIndex;
                customer[4] = this.completedCustomers[i].ServiceStartTime;
                customer[5] = this.completedCustomers[i].ServiceTime;
                customer[6] = this.completedCustomers[i].DepartureTime;
                customer[7] = this.completedCustomers[i].WaitTime;

                customersTable.Rows.Add(customer);

                if (this.completedCustomers[i].WaitTime > 0)
                {
                    totalWaitTime += this.completedCustomers[i].WaitTime;
                    customersWaited++;
                }
            }
            this.averageWaitingTime = totalWaitTime / (float)this.completedCustomers.Count;
            this.probabilityOfWait = (float)customersWaited / (float)this.completedCustomers.Count;

            DataRow serverData, idleServer;
            for (int i = 0; i < this.servers.Count; i++)
            {
                serverData = averageServiceTime.NewRow();
                serverData[0] = i + 1;
                serverData[1] = this.servers[i].AverageServiceTime;
                averageServiceTime.Rows.Add(serverData);

                idleServer = idleServerProbability.NewRow();
                idleServer[0] = i + 1;
                idleServer[1] = (this.clock - this.servers[i].TotalServiceTime) / this.clock;
                idleServerProbability.Rows.Add(idleServer);
            }

            //throw new NotImplementedException(@"Implement CalculateSystemPerformance in Simulation\Model.cs");

            //Calculate the value system performance measures
        }

    }
}
